import datetime
import json
import logging
import time

import paho.mqtt.client as mqtt

from MqttSign import AuthIfo

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)


class AliYunMQTTClient:
    def __init__(self, host, product_key, device_name, device_secret, log_callback=None):

        self.product_key = product_key
        self.device_name = device_name
        self.device_secret = device_secret

        self.time_stamp = str(int(time.time() * 1000))

        self.client_id = f"{product_key}.{device_name}"
        # EMS订阅的主题
        self.ems_sub_topic = [
            # 云端修改EMS下的配置
            f"/{product_key}/{device_name}/user/cc_config_push",
            # 操作功能列表（operation这个topic）
            f"/{product_key}/{device_name}/user/operation",
            # 写设备内部配置参数
            f"/{product_key}/{device_name}/user/dev_inside_config_push",
            # 运维管理-参数设置-控制策略-NMI设置-电网动态输出
            f"/{product_key}/{device_name}/user/der_push_data",
            # 校时
            f"/{product_key}/{device_name}/user/update_time",
            # api调度指令数据
            f"/{product_key}/{device_name}/user/api_dp_push_data",
            # 南澳dynamic export
            f"/{product_key}/{device_name}/user/der_push_data",
            # 切换emqx指令topic
            f"/cloud/{product_key}/{device_name}/emqx_info",

        ]
        # BMS订阅的主题
        self.bms_sub_topic = [
            # 操作功能列表（operation这个topic）
            f"/{product_key}/{device_name}/user/operation",
            # 切换emqx指令topic
            f"/cloud/{product_key}/{device_name}/emqx_info",
            f"/{product_key}/{device_name}/user/repush",
            f"/{product_key}/{device_name}/user/config_push",
        ]

        # 平台连接参数
        self.host = host
        self.port = 1883
        self.tls_crt = "root.crt"
        self.keep_alive = 300
        self.is_connected = False  # 连接状态
        self.stop_flag = False  # 停止MQTT客户端

        # 生成认证信息
        self.auth = AuthIfo()
        self.auth.calculate_sign_time(
            product_key, device_name, device_secret,
            self.client_id, self.time_stamp
        )

        # 创建MQTT客户端
        self.client = mqtt.Client(
            client_id=self.auth.mqttClientId,
            clean_session=True
        )
        # 初始化配置MQTT客户端参数，需要在connect之前生效，所以初始化
        self._configure_client()

        self.log_callback = log_callback  # 存储回调函数

    def _log(self, message, level=logging.INFO):
        """统一日志处理方法"""
        # 调用回调函数（如果存在）
        if self.log_callback:
            self.log_callback(message)

        # 原有日志记录（可选保留）
        logger.log(level, message)

    def _configure_client(self):
        """配置MQTT客户端参数"""
        self.client.username_pw_set(
            self.auth.mqttUsername,
            self.auth.mqttPassword
        )
        # 不配置tls_set
        # self.client.tls_set(self.tls_crt)

        # 设置回调函数，回调函数不需要带上()，需要的是函数引用
        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message
        self.client.on_disconnect = self._on_disconnect
        self.client.on_subscribe = self._on_subscribe

    def _on_connect(self, client, userdata, flags, rc):
        """当连接建立时由 Paho 库自动调用，连接回调处理"""
        if rc == 0:
            print("连接成功")
            self.is_connected = True
            self._log("Connected to Aliyun IoT Cloud successfully", logging.INFO)
            logger.info("Connected to Aliyun IoT Cloud successfully")
            # 连接成功后订阅主题
            self.subscribe_topics()
        else:
            print("_on_connect连接失败")
            self.is_connected = False
            error_msg = {
                1: "Incorrect protocol version",
                2: "Invalid client identifier",
                3: "Server unavailable",
                4: "Bad username or password",
                5: "Not authorized"
            }.get(rc, f"Unknown error: {rc}")
            self._log(f"连接失败: {error_msg}，请重新连接IOT")
            logger.error(f"Connection failed: {error_msg}")
            # 连接失败时尝试重连
            self._reconnect()

    def _on_message(self, client, userdata, msg):
        """客户端订阅成功以后，收到消息接收回调"""
        try:
            payload = msg.payload.decode()
            self._log(f"收到的主题和信息 [{msg.topic}]:\n {payload}")
            logger.info(f"Received message on topic [{msg.topic}]:\n {payload}")

        except Exception as e:
            self._log(f"错误处理消息: {str(e)}")
            logger.error(f"Error processing message: {str(e)}")

    def _on_disconnect(self, client, userdata, rc):
        """断开连接回调"""
        self.is_connected = False
        if rc != 0:
            print("异常断开")
            self._log(f"意外断开： (rc={rc})，请重新连接IOT")
            logger.warning(f"Unexpected disconnection (rc={rc})")
            self.stop()
            # 意外中断重连（出现的可能：同一个设备的三元组信息被其他客户端登录了，会出现rc=7）
            # self._reconnect()
        elif rc == 0:
            print("正常断开")
            return

    def _on_subscribe(self, client, userdata, mid, granted_qos):
        """订阅成功回调"""
        # self._log(f"订阅具有QoS的主题: {granted_qos[0]}")
        logger.info(f"Subscribed to topic with QoS: {granted_qos[0]}")

    def connect(self):
        """建立MQTT连接"""
        try:
            if self.is_connected:
                return True
            self._log("正在连接MQTT代理")
            logger.info("Connecting to MQTT broker...")
            self.client.connect(self.host, self.port, self.keep_alive)
            # 启动网络循环（非阻塞）
            self.client.loop_start()
            return True
        except Exception as e:
            self._log("连接错误，请重新连接")
            logger.error(f"Connection error: {str(e)}")
            return False

    def _reconnect(self):
        """内部重连处理"""
        if self.stop_flag:
            return
        self._log("10秒后重新连接")
        logger.info("Reconnecting in 10 seconds...")
        time.sleep(10)
        self.connect()

    def subscribe_topics(self):
        """订阅主题"""
        # BMS设备订阅的主题：
        if self.device_name[0:2] == "AB" or self.device_name[0:2] == "ab":
            for i in self.bms_sub_topic:
                try:
                    result = self.client.subscribe(i, qos=1)
                    if result[0] == mqtt.MQTT_ERR_SUCCESS:
                        self._log(f"BMS订阅的主题: {i},QoS：1")
                        logger.info(f"Subscribed to topic: {i},QoS：1")
                    else:
                        self._log(f"订阅失败：{result[0]}")
                        logger.error(f"Subscription failed with code: {result[0]}")
                except Exception as e:
                    self._log("订阅失败: {str(e)}")
                    logger.error(f"Subscription error: {str(e)}")
        # EMS订阅的主题
        else:
            for i in self.ems_sub_topic:
                try:
                    result = self.client.subscribe(i, qos=1)
                    if result[0] == mqtt.MQTT_ERR_SUCCESS:
                        self._log(f"EMS订阅的主题: {i},QoS：1")
                        logger.info(f"Subscribed to topic: {i},QoS：1")
                    else:
                        self._log(f"订阅失败：{result[0]}")
                        logger.error(f"Subscription failed with code: {result[0]}")
                except Exception as e:
                    self._log("订阅失败: {str(e)}")
                    logger.error(f"Subscription error: {str(e)}")

    def publish(self, topic, message):
        """
        发布消息到指定主题
        :param topic: 完整主题路径
        :param message: 消息内容（字符串或字典）
        """
        if not self.is_connected:
            self._log("未连接，无法发布消息")
            logger.warning("Not connected, cannot publish message")
            return False

        try:
            # 如果消息是字典，转换为JSON字符串
            if isinstance(message, dict):
                payload = json.dumps(message, ensure_ascii=False)
            else:
                payload = str(message)

            result = self.client.publish(topic, payload, qos=1)

            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                self._log(f"消息发布至主题：[{topic}]：\n{payload}")
                logger.info(f"Message published to {topic}: {payload}...")
                return True
            else:
                self._log(f"RC发布失败: {result.rc}")
                logger.warning(f"Publish failed with RC: {result.rc}")
                return False

        except Exception as e:
            self._log(f"发布错误：{str(e)}")
            logger.error(f"Publish error: {str(e)}")
            return False

    def start(self):
        """启动MQTT客户端"""
        self.stop_flag = False  # 开始时，需要把停止mqtt标志位置为False
        if not self.is_connected:
            self.connect()
        else:
            self._log("设备'" + self.device_name + "'已连接IOT")

    def stop(self):
        """停止MQTT客户端"""
        self.stop_flag = True
        try:
            self.client.loop_stop()
            self.client.disconnect()
            self._log("MQTT客户端停止并断开连接")
            logger.info("MQTT client stopped and disconnected")
        except:
            pass


if __name__ == "__main__":
    # DEVICE_CONFIG = {
    #     "host": "iot-060a965t.mqtt.iothub.aliyuncs.com",
    #     "product_key": "k0nd9zH6Bt4",
    #     "device_name": "AF50103250500001",
    #     "device_secret": "b153a4e4cd04bf801d4fb54600a9d2d2"
    # }
    # mqtt_client = AliYunMQTTClient(**DEVICE_CONFIG)
    # mqtt_client.start()
    """ 
    try:
        # 示例：保持主线程运行，模拟应用运行
        while True:
            time.sleep(1)

            # 示例：定时发布消息（实际使用中可按需调用）
            if int(time.time()) % 5 == 0:
                topic = f"/{mqtt_client.product_key}/{mqtt_client.device_name}/user/ems"
                message = {"ems_sn": "AF5010325050000101", "type": "system", "subtype": "ems", "ID": 16777216,
                           "time": datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"), "sys_rate_power_v5": 123,
                           "DI1": "2", "DI10": "2",
                           "DI11": "2", "DI12": "2", "DI13": "2", "DI14": "2", "DI15": "2", "DI16": "2", "DI17": "0",
                           "DI18": "0", "DI19": "0", "DI2": "2", "DI20": "0", "DI21": "0", "DI22": "0", "DI23": "0",
                           "DI24": "0", "DI25": "0", "DI26": "0", "DI27": "0", "DI28": "0", "DI29": "0", "DI3": "2",
                           "DI30": "0", "DI31": "0", "DI32": "0", "DI4": "2", "DI5": "2", "DI6": "2", "DI7": "2",
                           "DI8": "2", "DI9": "2", "allow_charge_energy": "29.300", "allow_charge_power": "50.000",
                           "allow_discharge_energy": "23.000", "allow_discharge_power": "50.000",
                           "backup_load_pac": "20.800", "com_res5": "0", "comm_status": "4",
                           "day_charge_energy": "150.800", "day_discharge_energy": "107.500", "di_sw_state": "0",
                           "do_sw_state": "0", "ems_fault1": "0", "ems_fault2": "32770", "ems_fault3": "0",
                           "ems_fault4": "0", "energy_res1": "0.000", "energy_res2": "0.000", "energy_res3": "0.000",
                           "energy_res4": "0.000", "energy_res5": "0.000", "energy_res6": "0.000",
                           "grid_load_pac": "1.000", "power_res1": "0.000", "power_res2": "0.000",
                           "power_res3": "0.000", "power_res4": "0.000", "power_res5": "0.000", "power_res6": "0.000",
                           "protocol_ver": "116", "total_charge_energy": "11647", "total_discharge_energy": "11127",
                           "total_load_pac": "21.800", "total_pac": "21.800", "total_preac": "-1.500",
                           "ems_status": "2", "work_mode_v5": 85, "attach_mode": "2", "main_scada_comm_status": 1,
                           "update_target": "7", "ems_mode": "3", "run_mode": "40000", "dual_power_state": "2",
                           "pv_rate_pact_v5": 2}
                mqtt_client.publish(topic, message)

    except KeyboardInterrupt:
        logger.info("Received keyboard interrupt, stopping client...")
    finally:
        mqtt_client.stop()
"""
    # ==============示例2================
    # 创建并启动客户端
    # 设备凭证配置（请替换为您的实际凭证）

    # 设备凭证配置（请替换为您的实际凭证）
    DEVICE_CONFIG = {
        "host": "iot-060a965t.mqtt.iothub.aliyuncs.com",
        "product_key": "k0nd9zH6Bt4",
        "device_name": "AF50103250500001",
        "device_secret": "b153a4e4cd04bf801d4fb54600a9d2d2"
    }

    # 创建并启动客户端
    mqtt_client = AliYunMQTTClient(**DEVICE_CONFIG)
    mqtt_client.start()
    time.sleep(10)

    # 示例：定时发布消息
    topic_01 = f"/{mqtt_client.product_key}/{mqtt_client.device_name}/user/ems"
    message_01 = {"ems_sn": "AF5010325050000101", "type": "system", "subtype": "ems", "ID": 16777216,
                  "time": datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"), "sys_rate_power_v5": 123,
                  "DI1": "2", "DI10": "2", "ems_status": "2"}
    mqtt_client.publish(topic_01, message_01)

    topic_02 = f"/{mqtt_client.product_key}/{mqtt_client.device_name}/user/mc_ied"
    message_02 = {"ems_sn": "AF5010325050000101", "type": "other", "subtype": "mc_ied", "ID": 117702656,
                  "time": datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"), "temp_a": 428, "temp_b": 429,
                  "temp_c": 430, "core_temp": 431, "mc_ied_fault1": 432, "mc_ied_fault2": 433, "mc_ied_fault3": 434,
                  "mc_ied_fault4": 435, "comm_status": 0}
    mqtt_client.publish(topic_02, message_02)
    time.sleep(10)
